// Copyright (c) 2005-2008, Simon Howard
// Permission to use, copy, modify, and/or distribute this software
// for any purpose with or without fee is hereby granted, provided
// that the above copyright notice and this permission notice appear
// in all copies.
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
// NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

#![allow(dead_code)]

use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::collections::HashMap;
use std::mem;

const NUM_TEST_VALUES: usize = 10000;

// Helper function to generate a hash for a string key
fn string_hash(key: &str) -> u64 {
    let mut hasher = DefaultHasher::new();
    key.hash(&mut hasher);
    hasher.finish()
}

// Helper function to compare two strings for equality
fn string_equal(a: &str, b: &str) -> bool {
    a == b
}

// Helper function to generate a hash for an integer key
fn int_hash(key: &i32) -> u64 {
    let mut hasher = DefaultHasher::new();
    key.hash(&mut hasher);
    hasher.finish()
}

// Helper function to compare two integers for equality
fn int_equal(a: &i32, b: &i32) -> bool {
    a == b
}

// Generates a hash table for use in tests containing 10,000 entries
fn generate_hash_table() -> HashMap<String, String> {
    let mut hash_table = HashMap::new();

    for i in 0..NUM_TEST_VALUES {
        let key = format!("{}", i);
        hash_table.insert(key.clone(), key);
    }

    hash_table
}

// Basic allocate and free
#[test]
fn test_hash_table_new_free() {
    let mut hash_table: HashMap<i32, i32> = HashMap::new();

    // Add some values
    hash_table.insert(1, 1);
    hash_table.insert(2, 2);
    hash_table.insert(3, 3);
    hash_table.insert(4, 4);

    // Test out of memory scenario (not applicable in Rust)
    // Rust's memory safety guarantees make this scenario irrelevant
}

// Test insert and lookup functions
#[test]
fn test_hash_table_insert_lookup() {
    let hash_table = generate_hash_table();

    assert_eq!(hash_table.len(), NUM_TEST_VALUES);

    // Check all values
    for i in 0..NUM_TEST_VALUES {
        let key = format!("{}", i);
        assert_eq!(hash_table.get(&key), Some(&key));
    }

    // Lookup on invalid values returns None
    assert_eq!(hash_table.get("-1"), None);
    assert_eq!(hash_table.get(&format!("{}", NUM_TEST_VALUES)), None);

    // Insert overwrites existing entries with the same key
    let key = "12345".to_string();
    let value = "hello world".to_string();
    let old_value = hash_table.insert(key.clone(), value.clone());
    assert_eq!(hash_table.get(&key), Some(&value));
}

// Test remove function
#[test]
fn test_hash_table_remove() {
    let mut hash_table = generate_hash_table();

    assert_eq!(hash_table.len(), NUM_TEST_VALUES);
    let key = "5000".to_string();
    assert!(hash_table.get(&key).is_some());

    // Remove an entry
    hash_table.remove(&key);

    // Check entry counter
    assert_eq!(hash_table.len(), NUM_TEST_VALUES - 1);

    // Check that None is returned now
    assert!(hash_table.get(&key).is_none());

    // Try removing a non-existent entry
    hash_table.remove("-1");
    assert_eq!(hash_table.len(), NUM_TEST_VALUES - 1);
}

// Test iterating over the hash table
#[test]
fn test_hash_table_iterating() {
    let hash_table = generate_hash_table();

    // Iterate over all values in the table
    let count = hash_table.iter().count();
    assert_eq!(count, NUM_TEST_VALUES);

    // Test iterating over an empty table
    let empty_hash_table: HashMap<i32, i32> = HashMap::new();
    let count = empty_hash_table.iter().count();
    assert_eq!(count, 0);
}

// Demonstrates the ability to iteratively remove objects from the hash table
#[test]
fn test_hash_table_iterating_remove() {
    let mut hash_table = generate_hash_table();

    // Iterate over all values in the table
    let mut removed = 0;
    let mut keys_to_remove = Vec::new();

    for (key, _) in &hash_table {
        if key.parse::<i32>().unwrap() % 100 == 0 {
            keys_to_remove.push(key.clone());
            removed += 1;
        }
    }

    for key in keys_to_remove {
        hash_table.remove(&key);
    }

    // Check counts
    assert_eq!(removed, 100);
    assert_eq!(hash_table.len(), NUM_TEST_VALUES - removed);

    // Check all entries divisible by 100 were really removed
    for i in 0..NUM_TEST_VALUES {
        let key = format!("{}", i);
        if i % 100 == 0 {
            assert!(hash_table.get(&key).is_none());
        } else {
            assert!(hash_table.get(&key).is_some());
        }
    }
}

// Test the use of functions to free keys / values when they are removed
#[test]
fn test_hash_table_free_functions() {
    let mut hash_table: HashMap<i32, i32> = HashMap::new();

    // Create a hash table, fill it with values
    for i in 0..NUM_TEST_VALUES {
        hash_table.insert(i, 99);
    }

    assert_eq!(hash_table.len(), NUM_TEST_VALUES);

    // Check that removing a key works
    hash_table.remove(&(NUM_TEST_VALUES / 2));
    assert_eq!(hash_table.len(), NUM_TEST_VALUES - 1);

    // Check that replacing an existing key works
    hash_table.insert(NUM_TEST_VALUES / 3, 999);
    assert_eq!(hash_table.len(), NUM_TEST_VALUES - 1);
}

// Test for out of memory scenario (not applicable in Rust)
#[test]
fn test_hash_table_out_of_memory() {
    let mut hash_table: HashMap<i32, i32> = HashMap::new();

    // Test normal failure (not applicable in Rust)
    // Rust's memory safety guarantees make this scenario irrelevant

    // Test failure when increasing table size (not applicable in Rust)
    // Rust's memory safety guarantees make this scenario irrelevant
}

// Test iterating over the hash table and retrieving both key and value
#[test]
fn test_hash_iterator_key_pair() {
    let mut hash_table: HashMap<i32, i32> = HashMap::new();

    // Add some values
    hash_table.insert(1, 1);
    hash_table.insert(2, 2);

    for (key, value) in &hash_table {
        assert_eq!(key, value);
    }
}

fn main() {
    // Run tests
    let test_result = std::panic::catch_unwind(|| {
        test_hash_table_new_free();
        test_hash_table_insert_lookup();
        test_hash_table_remove();
        test_hash_table_iterating();
        test_hash_table_iterating_remove();
        test_hash_table_free_functions();
        test_hash_table_out_of_memory();
        test_hash_iterator_key_pair();
    });

    if test_result.is_err() {
        eprintln!("Tests failed!");
        std::process::exit(1);
    }
}
